using System;
using System.Net;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.NetduinoPlus;

using WebServer;
using SDCard;

//using NetMf.CommonExtensions;// needs dll downloaded from http://netmfcommonext.codeplex.com/releases/54949/download/162651
// also see http://microlinq.codeplex.com/ linq stuff



public class A2DInput
{
    public float CalibrationScalar = 1;
    public float CalibrationOffset = 0;
    private SecretLabs.NETMF.Hardware.AnalogInput _AnalogInput = null;
    private float[] _TemperatureSamples;



    public A2DInput(Cpu.Pin pin, uint samplesize, float calibrationScalar, float calibrationOffset)
    {
        CalibrationScalar = calibrationScalar;
        CalibrationOffset = calibrationOffset;
        _AnalogInput = new SecretLabs.NETMF.Hardware.AnalogInput(pin);
        _TemperatureSamples = new float[samplesize];
        for (int i = 0; i < samplesize; i++)
        {
            _TemperatureSamples[i] = 0;
        }
    }

    public void SampleFromPin(int sampleIndex)
    {
        //Reads the input and converts it to celcius degrees
        float currentReading = _AnalogInput.Read();
        float temp_in_kelvin = currentReading / 1023 * 3.078f * 100 * CalibrationScalar; //100 is for 10mV/C
        _TemperatureSamples[sampleIndex] = temp_in_kelvin - 273.15f + CalibrationOffset;
        Thread.Sleep(10);
    }

    public float GetAverage()
    {
        float sum = 0;
        for (int i = 0; i < _TemperatureSamples.Length; i++)
        {
            sum += _TemperatureSamples[i];
        }
        float average = sum / _TemperatureSamples.Length; // calculate the average of all samples.
        return average;
    }

    public string GetAverageString()
    {
        return GetAverage().ToString("F");
//        return StringUtility.Format("{0:F2}", GetAverage());
    }

}

public class A2D
{// should only be one instance anyway so static doesn't really matter.
    public static string LogFileName = "TimeUnknown.txt";
    public static string LogFileDirectory = ""; 
    public const int InputCount = 6;
    const uint _SampleSize = 16; // No. of samples the device takes per reading.
    const uint _MillisecondsBetweenNTPTimeChecks = 10 * 60 * 1000;
    const uint _MillisecondsBetweenConsoleWrites = 3 * 1000;
    const uint _MillisecondsBetweenTemperatureLogs = 5 * 60 * 1000; 
    const float initialScalar = 2.894f / 2.25f;//1.002005f;//(32 + 273.15) /(26 + 273.15)// probably different for each sensor
    protected DateTime _LastTimeCheck = NetduinoTime.UninitialisedTime;
    protected DateTime _LastConsoleWriteTime = NetduinoTime.UninitialisedTime;
    protected DateTime _LastTemperatureLogTime = NetduinoTime.UninitialisedTime;
    private bool _Pause = false;

    public A2D()
    {
        LogFileDirectory = SDCard.SDCard.MountDirectoryPath + "\\";

    }

    public void MainLoop(float[] temperatureInCelsius)
    {
        // Rev B boards use internal AREF by default: switch to external AREF
        OutputPort arefSelect = new OutputPort((Cpu.Pin)56, false);

        SDCard.SDCard sdCard = new SDCard.SDCard();
        A2DInput[] a2DInputs = new A2DInput[InputCount]; // Assign analog PIN A0 as input port.
//        a2DInputs[0] = new A2DInput(Pins.GPIO_PIN_A2, _SampleSize, initialScalar); // Assign analog PIN A0 as input port. todo remove 
        int sampleIndex = 0;


        //100: 108.3, ?,?
        //39.5: 46.6, 45.4, 44.3
        //0.5: 8, 6.5 , 5.9

        // -7.63		-5.95		-4.95
        const float a2d3Offset = -7.63f;
        const float a2d4Offset = -5.95f;
        const float a2d5Offset = -4.95f;
        a2DInputs[0] = new A2DInput(Pins.GPIO_PIN_A0, _SampleSize, initialScalar, 0); // Assign analog PIN A0 as input port.
        a2DInputs[1] = new A2DInput(Pins.GPIO_PIN_A1, _SampleSize, initialScalar, 0); // Assign analog PIN A0 as input port.
        a2DInputs[2] = new A2DInput(Pins.GPIO_PIN_A2, _SampleSize, initialScalar, 0); // Assign analog PIN A0 as input port.
        a2DInputs[3] = new A2DInput(Pins.GPIO_PIN_A3, _SampleSize, initialScalar, a2d3Offset); // Assign analog PIN A0 as input port.
        a2DInputs[4] = new A2DInput(Pins.GPIO_PIN_A4, _SampleSize, initialScalar, a2d4Offset); // Assign analog PIN A0 as input port.
        a2DInputs[5] = new A2DInput(Pins.GPIO_PIN_A5, _SampleSize, initialScalar, a2d5Offset); // Assign analog PIN A0 as input port.
        PWM led = new PWM(Pins.GPIO_PIN_D10); // Assign GPIO PIN D10 as LED output port.

        while (true) // loop infinitely
        {
            if (_Pause)
            {// only stop thread execution away from critical sections to avoid deadlocks
                Thread.Sleep(500);
            }
            else
            {
                try
                {
                 //   arefSelect.Write(false);// something seems to set this back to true when a new thread is launched??? surely this is not needed
                    if ((_LastTimeCheck.AddMilliseconds(_MillisecondsBetweenNTPTimeChecks) < DateTime.Now) || (_LastTimeCheck == NetduinoTime.UninitialisedTime))
                    {
                        if (_LastTimeCheck == NetduinoTime.UninitialisedTime)
                        {
                            Thread.Sleep(2000);
                        }
                        DateTime tempTime = NetduinoTime.NTPTime("ntp.internode.on.net", +10);
                        if (tempTime != NetduinoTime.UninitialisedTime)
                        {
                            Utility.SetLocalTime(tempTime);
                            _LastTimeCheck = tempTime;
                            int WeekOfYear = _LastTimeCheck.DayOfYear / 7;// new file every 7 days.
                            LogFileName = "TemperatureLog_" + _LastTimeCheck.Year.ToString() + "-" + WeekOfYear.ToString() + ".txt";
                            LogFileDirectory = SDCard.SDCard.MountDirectoryPath + "\\";
                        }
                        ConsoleWrite.CollectMemoryAndPrint(true, Thread.CurrentThread.ManagedThreadId);
                    }
                    if (_LastTemperatureLogTime == NetduinoTime.UninitialisedTime)
                    {
                        _LastTemperatureLogTime = DateTime.Now.AddMilliseconds(_MillisecondsBetweenTemperatureLogs * -.6);
                    }
                    Thread.Sleep(10); // allow system to complete proccessing.

                    for (int i = 0; i < a2DInputs.Length; i++)
                    {
                        a2DInputs[i].SampleFromPin(sampleIndex);
                    }
                    sampleIndex++;
                    if (sampleIndex >= _SampleSize)
                        sampleIndex = 0;

                    //{
                    //    float temperature = GetTemperature(analogInputs[0]);
                    //    temperatureInCelsius[0] = temperature;
                    //    _TemperatureStrings[0] = StringUtility.Format("{0:F2}", temperature);
                    //    ConsoleWrite.Print(dateTimeString.Substring(0, dateTimeString.Length - 1) + " : " + _TemperatureStrings[0]);
                    //}
                    //{
                    //    float temperature = GetTemperature(analogInputs[1]);
                    //    temperatureInCelsius[1] = temperature;
                    //    _TemperatureStrings[1] = StringUtility.Format("{0:F2}", temperature);
                    //    ConsoleWrite.Print(dateTimeString.Substring(0, dateTimeString.Length - 1) + " : " + _TemperatureStrings[1]);
                    //}

                    if ((_LastTemperatureLogTime.AddMilliseconds(_MillisecondsBetweenTemperatureLogs) < DateTime.Now))// todo log if significant changes
                    {
                        _LastTemperatureLogTime = DateTime.Now;
                        string outputString = GetLogLine(_LastTemperatureLogTime, a2DInputs);
                        sdCard.WriteLine(LogFileDirectory, LogFileName, outputString);
                        ConsoleWrite.Print("Logged, " + outputString);
                    } 

                    if ((_LastConsoleWriteTime.AddMilliseconds(_MillisecondsBetweenConsoleWrites) < DateTime.Now))// todo log if significant changes
                    {
                        _LastConsoleWriteTime = DateTime.Now;
                        string outputString = GetLogLine(_LastConsoleWriteTime, a2DInputs);
                        ConsoleWrite.Print(outputString);
                    }
                    UpdateWebTemperatures(a2DInputs, temperatureInCelsius);
                    Thread.Sleep(300); // allow system to complete proccessing.
                }
                catch (Exception ex)
                {
                    ConsoleWrite.Print("Exception in A2D.Main: " + ex.Message);
                    Thread.Sleep(2000); // allow system to have a rest.
                }
            }
        }
    }

    public void PauseThread(bool stopExecution)
    {
        _Pause = stopExecution;
        Thread.Sleep(300); // this line pauses the caller not the a2d, gives time for a2d to stop
    }

    string GetLogLine(DateTime currentDateTime, A2DInput[] a2DInputs)
    {
        string dateTimeString = currentDateTime.ToString("u");
        string result = dateTimeString.Substring(0, dateTimeString.Length - 1) + " :: ";
        for (int i = 0; i < a2DInputs.Length; i++)
        {
            //                        a2DInputs[i].SampleFromPin();
            result += "pin " + i.ToString() + " : " + a2DInputs[i].GetAverageString() + "; ";
        }
        return result;
    }

    private void UpdateWebTemperatures(A2DInput[] a2DInputs, float[] temperatureInCelsius)
    {
        for (int i = 0; i < a2DInputs.Length; i++)
        {
            temperatureInCelsius[i] = a2DInputs[i].GetAverage();
        }
    }

    public static long constrain(long value, long low, long high)
    {
        return value >= high ? high : value <= low ? low : value;
    }

    public static long map(long value, long fromLow, long fromHigh, long toLow, long toHigh)
    {
        try
        {
            return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }


}